package org.crud4j.core.defprovider;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

import org.apache.commons.lang.ClassUtils;
import org.crud4j.core.CrudBeanDef;
import org.crud4j.core.CrudFieldDef;
import org.crud4j.core.annotation.CrudBean;
import org.crud4j.core.annotation.CrudBrowsable;
import org.crud4j.core.annotation.CrudBrowseFilter;
import org.crud4j.core.annotation.CrudEditDisabled;
import org.crud4j.core.annotation.CrudFieldEditor;
import org.crud4j.core.annotation.CrudForeignKey;
import org.crud4j.core.annotation.CrudGeneratedValue;
import org.crud4j.core.annotation.CrudHidden;
import org.crud4j.core.annotation.CrudId;
import org.crud4j.core.annotation.CrudIdClass;
import org.crud4j.core.annotation.CrudInstanceLabel;
import org.crud4j.core.annotation.CrudLabel;
import org.crud4j.core.annotation.CrudLength;
import org.crud4j.core.annotation.CrudNewDisabled;
import org.crud4j.core.annotation.CrudNullable;
import org.crud4j.core.annotation.CrudRemoveDisabled;
import org.crud4j.core.annotation.CrudRemoveMark;
import org.crud4j.core.annotation.CrudRespectEmptyValue;
import org.crud4j.core.annotation.CrudValidation;
import org.crud4j.core.annotation.CrudViewDisabled;
import org.crud4j.core.annotation.FieldEditor;

/**
 * Annotation based {@link CrudBeanDefProvider provider}
 * 
 * @see {@link CrudBeanDef bean definition}
 */
public class AnnCrudDefProvider implements CrudBeanDefProvider {

	/**
	 * Updates the def based on crud annotations
	 */
	@SuppressWarnings("unchecked")
	public void updateDef(Class type, CrudBeanDef def) {

		if (!type.isAnnotationPresent(CrudBean.class)) {
			throw new IllegalArgumentException(
					"the given type is not a crud bean");
		}

		// id type
		if (type.isAnnotationPresent(CrudIdClass.class)) {
			CrudIdClass idClass = (CrudIdClass) type
					.getAnnotation(CrudIdClass.class);
			def.setIdType(idClass.value());
		}
		//View disabled
		if (type.isAnnotationPresent(CrudViewDisabled.class)){
			def.setViewDisabled(true);
		}

		// new disabled
		if (type.isAnnotationPresent(CrudNewDisabled.class)) {
			def.setNewDisabled(true);
		}

		// edit disabled
		if (type.isAnnotationPresent(CrudEditDisabled.class)) {
			def.setEditDisabled(true);
		}

		// remove disabled
		if (type.isAnnotationPresent(CrudRemoveDisabled.class)) {
			def.setRemoveDisabled(true);
		}

		// instance label expression
		if (type.isAnnotationPresent(CrudInstanceLabel.class)) {
			def.setInstanceLabelExpression(((CrudInstanceLabel) type
					.getAnnotation(CrudInstanceLabel.class)).value());
		}

		// Label
		if (type.isAnnotationPresent(CrudLabel.class)) {
			def.setLabel(((CrudLabel) type.getAnnotation(CrudLabel.class))
					.value());
		}

		this.addFieldsDef(type, def);

	}

	/**
	 * Add field definitions to the bean definition
	 * 
	 * @param type
	 * @param def
	 */
	private void addFieldsDef(Class type, CrudBeanDef def) {
		// fields
		Map<String, CrudFieldDef> fields = def.getFieldDefsMap();

		for (Method m : type.getMethods()) {
			// filter only getter methods
			// FIXME check other methods ?
			if (m.getName().startsWith("get")
					&& !m.getName().equals("getClass")) {

				// name
				String firstChar = m.getName().substring(3).substring(0, 1);
				firstChar = firstChar.toLowerCase();
				String name = firstChar + m.getName().substring(4);

				// Create a CrudField
				CrudFieldDef field = fields.get(name);

				// FIXME: ignore collections
				if (m.isAnnotationPresent(CrudHidden.class)
						|| m.getReturnType().equals(Collection.class)
						|| ClassUtils.getAllInterfaces(m.getReturnType())
								.contains(Collection.class)) {
					field.setHidden(true);
				}

				// generated values
				if (m.isAnnotationPresent(CrudGeneratedValue.class)) {
					field.setGeneratedValue(true);
					field.setEditDisabled(true);
					field.setNewDisabled(true);
				}

				// id
				if (m.isAnnotationPresent(CrudId.class)) {
					field.setId(true);
					// in case the id isn't generated automatically the
					// field shouldn't be able to accept null values
					field.setNullable(false);
				} else {
					// In case it's not an id let's evaluate nulleable
					if (m.isAnnotationPresent(CrudNullable.class)) {
						field.setNullable(m.getAnnotation(CrudNullable.class)
								.value());
					}
				}

				// read length
				if (m.isAnnotationPresent(CrudLength.class)) {
					field.setLength(m.getAnnotation(CrudLength.class).value());
				}

				// crud bean (fk)
				if (m.getReturnType().isAnnotationPresent(CrudBean.class)) {
					field.setCrudBean(true);
				}

				// crudfk
				if (m.isAnnotationPresent(CrudForeignKey.class)) {
					field.setCrudFk(true);
					field.setCrudFkClass(m.getAnnotation(CrudForeignKey.class)
							.value());
				}

				// label
				if (m.isAnnotationPresent(CrudLabel.class)) {
					CrudLabel labelAnn = m.getAnnotation(CrudLabel.class);
					field.setLabel(capitalizeLabel(labelAnn.value()));
				} else {
					// try to find default label
					// TODO Lookup in a resourcebundle whether a defaul label
					// exists

				}

				// type
				field.setType(m.getReturnType());

				// editor
				if (m.isAnnotationPresent(CrudFieldEditor.class)) {
					CrudFieldEditor editorAnn = m
							.getAnnotation(CrudFieldEditor.class);
					field.setEditor(editorAnn.value().getPage());
				} else if (m.getReturnType()
						.isAnnotationPresent(CrudBean.class)) {
					field.setEditor(FieldEditor.FK.getPage());
				} else if (m.isAnnotationPresent(CrudForeignKey.class)) {
					field.setEditor(FieldEditor.CRUDFK.getPage());
				} else {
					// use default if not specified
					field.setEditor(FieldEditor.getDefaultEditor(
							field.getType()).getPage());
				}

				// browsable
				if (m.isAnnotationPresent(CrudBrowsable.class)) {
					CrudBrowsable ann = m.getAnnotation(CrudBrowsable.class);
					field.setBrowsable(true);
					field.setPosition(ann.value());
				}

				// empty field value
				if (m.isAnnotationPresent(CrudRespectEmptyValue.class)) {
					field.setRespectEmptyValue(true);
				}

				// remove mark
				if (m.isAnnotationPresent(CrudRemoveMark.class)) {
					field.setRemoveMark(true);
					field.setHidden(true);
				}

				// editable
				if (m.isAnnotationPresent(CrudEditDisabled.class)) {
					field.setEditDisabled(true);
				}

				// editable on creation
				if (m.isAnnotationPresent(CrudNewDisabled.class)) {
					field.setNewDisabled(true);
				}

				// crud browse filter
				if (m.isAnnotationPresent(CrudBrowseFilter.class)) {
					field.setBrowseFilter(true);
				}

				if (m.isAnnotationPresent(CrudValidation.class)) {
					CrudValidation ann = m.getAnnotation(CrudValidation.class);
					field.setValidationExpression(ann.regexp());
					field.setValidationMessage(ann.message());
				}
			}
		}
	}

	private String capitalizeLabel(String label) {
		if (label != null && !label.trim().equals("")) {
			StringBuilder capitalizedLabel = new StringBuilder();
			capitalizedLabel.append(label.substring(0, 1).toUpperCase());
			capitalizedLabel.append(label.substring(1));
			return capitalizedLabel.toString();
		} else {
			return label;
		}
	}

}
